/**
 * 20220304:[require] to [import]...jordia
 */
import {ShaderMaterial,Color,PlaneGeometry,Box3,Object3D,Mesh,Vector3} from 'three';

/**
 * 边框材质
 */
class FrameMaterial extends ShaderMaterial{
    constructor(values={}){
        super();
        this._values=values;
        this.init();
    }

    //--------------------------------对外属性及方法----------------------------------

    /**
     * 获取颜色
     * @returns {Color}
     */
    get color(){
        return this.uniforms.color.value;
    }

    /**
     * 设置颜色
     * @param color {Color}
     */
    set color(color){
        this.uniforms.color.value=color.clone();
    }

    /**
     * 获取边框强度
     * @returns {number}
     */
    get strength(){
        return this.uniforms.strength.value;
    }

    /**
     * 设置边框强度（值越大强度越低）
     * @param num {number}
     */
    set strength(num){
        this.uniforms.strength.value=num;
    }

    /**
     * 销毁
     */
    dispose(){
        super.dispose();
        this._values=null;
    }

    //------------------------------------私有属性及方法----------------------------------

    /**
     * 初始化
     * @private
     */
    init(){
        this.initUniforms();
        this.initVertexShader();
        this.initFragmentShader();
        this.initialize();
    }

    /**
     * 初始化组件状态
     * @private
     */
    initialize(){
        this.transparent=true;
        this.depthWrite=false;
    }

    /**
     * 初始化属性
     * @private
     */
    initUniforms(){
        this.uniforms={
            color:{value:this._values.color||new Color(0,1,0)},
            strength:{value:this._values.strength||0.5},
        };
    }

    /**
     * 初始化顶点着色器
     * @private
     */
    initVertexShader(){
        this.vertexShader=[
            'varying vec2 vuv;' ,
            'void main(){' ,
            '   gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.);' ,
            '   vuv=uv*2.-1.;' ,
            '}'
        ].join('\n');
    }

    /**
     * 初始化片元着色器
     * @private
     */
    initFragmentShader(){
        this.fragmentShader=[
            'uniform vec3 color;' ,
            'uniform float strength;' ,
            'varying vec2 vuv;' ,
            'void main(){' ,
            '   float d=max(abs(pow(vuv.x,4.)+pow(vuv.y,4.))-strength,0.);' ,
            '   gl_FragColor=vec4(color,d*d);' ,
            '}'
        ].join('\n');
    }
}
let PLANE_GEOMETRY=new PlaneGeometry(1,1);
let DEFAULT_MATERIAL=new FrameMaterial();

/**
 * 3D对象包围盒显示
 *
 * 示例：
 *      let bound=new VisualizeBounds();
 *      bound.scene=scene;  指定场景
 *      bound.offset=0.2;   设置偏移
 *      obj0.updateMatrixWorld();
 *      obj1.updateMatrixWorld();
 *      bound.addTargets(obj0,obj1);    显示obj0和obj1的包围盒
 *      obj0.applyMatrix(transform);
 *      obj0.updateMatrixWorld();
 *      bound.updateTargets(obj0);  obj0变换后进行更新
 *      bound.remove(obj0);     不显示obj0的包围盒
 */
export default class VisualizeBounds{
    constructor(){
        this.init();
    }

    //----------------------------------对外属性及方法----------------------------------------

    /**
     * 设置用于显示包围盒的场景
     * @param scene {Scene}
     */
    set scene(scene){
        if(this._scene&&scene.uuid!==this._scene.uuid) {
            if (this._bounds.length > 0) {
                console.warn('All objects should be removed before setting up a new scene!');
                return;
            }
        }
        this._scene=scene;
    }

    /**
     * 设置包围盒颜色（仅用于默认材质，否则请获取材质进行编辑）
     * @param color {Color}
     */
    set boundsColor(color){
        if(!this._material instanceof FrameMaterial){
            console.warn('property [boundsColor] only available for default material(FrameMaterial),' +
                'otherwise use [.material] to set the property style!');
            return;
        }
        this._material.color=color;
    }

    /**
     * 设置包围盒边框强度（仅用于默认材质，否则请获取材质进行编辑）
     * @param num {number}
     */
    set boundsStrength(num){
        if(!this._material instanceof FrameMaterial){
            console.warn('property [boundsStrength] only available for default material(FrameMaterial),' +
                'otherwise use [.material] to set the property style!');
            return;
        }
        this._material.strength=num;
    }

    /**
     * 获取包围盒大小偏移值
     * @returns {number}
     */
    get offset(){
        return this._offset;
    }

    /**
     * 设置包围盒大小偏移值（在计算所得包围盒大小的基础上再放大些）
     * @param num {number}
     */
    set offset(num){
        this._offset=num;
    }

    /**
     * 获取包围盒材质（每个面的）
     * @returns {Material}
     */
    get material(){
        return this._material;
    }

    /**
     * 设置包围盒材质（将取代默认材质）
     * @param material {Material}
     */
    set material(material){
        this._material=material;
    }

    /**
     * 获取对象的包围盒（世界空间，必须先添加）
     * @param object {Object3D}
     * @param out {Box3}
     * @returns {Box3}
     */
    getObjectBoundingBox(object,out=new Box3()){
        for(let i=0;i<this._targets.length;i++){
            if(this._targets[i].uuid===object.uuid){
                return out.copy(this._boxes[i]);
            }
        }
    }

    /**
     * 添加对象（显示其包围盒）
     * @param objects {...Object3D}
     */
    addTargets(...objects){
        if(!this._scene){
            console.warn('need a scene to show the bounds!');
            return;
        }
        for(let object of objects){
            let has=false;
            for(let target of this._targets){
                if(target.uuid===object.uuid){
                    has=true;
                    break;
                }
            }
            if(!has){
                this._targets.push(object);
                this.setBox(object);
                this.setBounds(object);
            }
        }
    }

    /**
     * 移除对象（取消其包围盒显示）
     * @param objects {...Object3D}
     */
    removeTargets(...objects){
        for(let object of objects){
            for(let i=0;i<this._targets.length;i++){
                let target=this._targets[i];
                if(target.uuid===object.uuid){
                    this.destroyBounds(i);
                    break;
                }
            }
        }
    }

    /**
     * 清除对象（取消所有包围盒显示）
     */
    clearTargets(){
        for(let i=this._targets.length-1;i>=0;i--){
            this.destroyBounds(i);
        }
    }

    /**
     * 更新对象的包围盒（对象进行变换后需要更新其新的尺寸和位置）
     * @param objects {...Object3D}
     */
    updateTargets(...objects){
        for(let object of objects){
            for(let i=0;i<this._targets.length;i++){
                if(object.uuid===this._targets[i].uuid){
                    this.updateBounds(i);
                    break;
                }
            }
        }
    }

    /**
     * 设置对象包围盒的渲染顺序
     * @param object {Object3D}
     * @param order {number}
     */
    setRenderOrder(object,order){
        for(let i=0;i<this._targets.length;i++){
            if(object.uuid===this._targets[i].uuid){
                this._bounds[i].renderOrder=order;
                break;
            }
        }
    }

    /**
     * 销毁
     */
    dispose(){
        this.clearTargets();
        PLANE_GEOMETRY.dispose();
        DEFAULT_MATERIAL.dispose();
        this._scene=null;
        this._targets=null;
        this._offset=null;
        this._bounds=null;
        this._boxes=null;
        this._material=null;
        this._tV0=null;
    }

    //-------------------------------------私有属性及方法----------------------------------------

    /**
     * 从队列中移除包围盒信息
     * @param index {number}
     * @private
     */
    destroyBounds(index){
        this._scene.remove(this._bounds[index]);
        this._targets.splice(index,1);
        this._boxes.splice(index,1);
        /**
         * 材质和几何体共用，此处不销毁
         */
        this._bounds.splice(index,1);
    }

    /**
     * 更新包围盒
     * @param index
     * @private
     */
    updateBounds(index){
        let box=this._boxes[index];
        let object=this._targets[index];
        let bounds=this._bounds[index];
        box.setFromObject(object);
        box.expandByScalar(this._offset);
        bounds.scale.set(box.max.x-box.min.x,box.max.y-box.min.y,box.max.z-box.min.z);
        box.getCenter(this._tV0);
        bounds.position.copy(this._tV0);
    }

    /**
     * 创建新的包围盒
     * @private
     */
    setBounds(){
        let box=this._boxes[this._boxes.length-1];
        box.getCenter(this._tV0);

        let bounds=new Object3D();
        let pl=new Mesh(PLANE_GEOMETRY,this._material);
        pl.position.set(-0.5,0,0);
        pl.rotation.set(0,-Math.PI*0.5,0);
        let pr=pl.clone();
        pr.position.set(0.5,0,0);
        pr.rotation.set(0,Math.PI*0.5,0);
        let pf=pl.clone();
        pf.position.set(0,0,0.5);
        pf.rotation.set(0,0,0);
        let pb=pl.clone();
        pb.position.set(0,0,-0.5);
        pb.rotation.set(0,Math.PI,0);
        let pt=pl.clone();
        pt.position.set(0,0.5,0);
        pt.rotation.set(-Math.PI*0.5,0,0);
        let pd=pl.clone();
        pd.position.set(0,-0.5,0);
        pd.rotation.set(Math.PI*0.5,0,0);
        bounds.add(pl,pr,pf,pb,pt,pd);

        bounds.scale.set(box.max.x-box.min.x,box.max.y-box.min.y,box.max.z-box.min.z);
        bounds.position.copy(this._tV0);
        this._bounds.push(bounds);
        this._scene.add(bounds);
    }

    /**
     * 创建新的box3
     * @param object
     * @private
     */
    setBox(object){
        let box=new Box3();
        box.expandByObject(object);
        box.expandByScalar(this._offset);
        this._boxes.push(box);
    }

    /**
     * 初始化
     * @private
     */
    init(){
        this.initVariable();
    }

    /**
     * 初始化变量
     * @private
     */
    initVariable(){
        this._scene=null;
        this._targets=[];
        this._offset=0.1;
        this._bounds=[];
        this._boxes=[];
        this._material=DEFAULT_MATERIAL;
        this._tV0=new Vector3();
    }
}